/*
File: Eagledev.c
Description: Function body of MRVDRV_ADAPTER
*/

#include "precomp.h"
#include "eagledev.h"
#include "txproc.h"
#include "intproc.h"
#include "rxproc.h"


#define MAX_DNLDRESUMEFUNCS		10
typedef struct _DnLdRmNode
{
	DNLDRESUMEFUNC			pFunc;
    PVOID					pArg;
    DNLD_TYPE               type;
} DNLDRMNODE, *PDNLDRMNODE;

typedef struct _DnLdPath
{
	CRITICAL_SECTION		DnLdCS;

	int						TicketNumber;
    //Callback functions, will be called if DnLd_Path is available
	int						FuncCnt;
    DNLDRMNODE              DnLdRmLst[MAX_DNLDRESUMEFUNCS];
    
    ///Masks, To maskout some type of DnLd_Path request
    UCHAR                   mask[DNLDT_MAX];
    TRNREASON               MaskReason[DNLDT_MAX];
} DNLDPARAM, *PDNLDPARAM;
static DNLDPARAM	DnldParam;
static NDIS_STATUS	InitDnldParam(IN PPVOID ipDnLdParam);
static NDIS_STATUS	DeinitDnldParam(IN PVOID ipDnLdParam);
static void DnldResumeFunc(PDNLDPARAM	pDnLdParam);

static NDIS_STATUS	CheckBeforeTx(PVOID *pFuncArg, PSDIO_TX_PKT pDnldPacket)
{
	PMRVDRV_ADAPTER pAdapter = (PMRVDRV_ADAPTER)pFuncArg;

    if ( pAdapter->MediaConnectStatus == NdisMediaStateDisconnected )
    { 
		CleanUpSingleTxBuffer( pAdapter->pTxParam);
		ResetRxPDQ(pAdapter->pRxParam); 
		return NDIS_STATUS_FAILURE;
    }

	// tt 060831: we need the following WMM_UAPSD for a compiler issue
    DBGPRINT(DBG_ALLEN|DBG_CCX,(L"[Marvell]SendSinglePacket Adapter->psState=%d\n",pAdapter->psState));


    if ((pAdapter->RoamingMode == SMLS_ROAMING_MODE) ? (wlan_roam_query_isConnecting(pAdapter->pwlanRoamParam) == TRUE):FALSE) {
        SetDnldMask(pAdapter->pDnLdParam, DNLDT_TX, L"Roaming");
        return NDIS_STATUS_SUCCESS;
   	}	
	

	return NDIS_STATUS_SUCCESS;
}

static NDIS_STATUS	EnhancePSBeforeTx(PVOID *pFuncArg, PSDIO_TX_PKT pDnldPacket)
{
	PMRVDRV_ADAPTER pAdapter = (PMRVDRV_ADAPTER)pFuncArg;
	
	if( pAdapter->ehancePS == FALSE) {
		goto FuncFinal;
	}
    if(pAdapter->bPSConfirm == TRUE)
    {                              
         //Driver is being in HS or IEEE PS state
         //in IEEE PS mode, ehance ps does not support PPS&UAPSD mode
         if( IsThisHostPowerState(pAdapter, HTWK_STATE_SLEEP) || 
            (  !IsThisHostPowerState(pAdapter, HTWK_STATE_SLEEP) &&  
               pAdapter->psState == PS_STATE_SLEEP &&
               pAdapter->PPS_Enabled == 0 && 
               pAdapter->sleepperiod ==0  )  )
         {
           DBGPRINT(DBG_HOSTSLEEP|DBG_WARNING,(L"SendSinglePacket: Pwr up device\r\n"));
           If_PowerUpDevice(pAdapter);
         }
    }
FuncFinal:
	return NDIS_STATUS_SUCCESS;
}
static NDIS_STATUS SetTxMaskForPwr(PVOID *pFuncArg, PSDIO_TX_PKT pDnldPacket)
{
    PMRVDRV_ADAPTER pAdapter = (PMRVDRV_ADAPTER)pFuncArg;
    NDIS_STATUS 	result = NDIS_STATUS_SUCCESS;
    PSDIO_TX_PKT_T   pPktBuf = (PSDIO_TX_PKT_T)(&pDnldPacket->Buf.TxDataBuf);

    while (pPktBuf->Wcb.NextWCBPtr != 0) {
        ///Traverse until the last packet.
        if (pPktBuf->Wcb.NextWCBPtr != 0) {
            ASSERT((pPktBuf->Wcb.PowerMgmt & MRVDRV_WCB_POWER_MGMT_LAST_PACKET)==0);
        }
        pPktBuf = (PSDIO_TX_PKT_T)((PUCHAR)pPktBuf + pPktBuf->Wcb.NextWCBPtr);
    }
    if (pPktBuf->Wcb.PowerMgmt & MRVDRV_WCB_POWER_MGMT_LAST_PACKET) 
    {
        if (((pAdapter->PPS_Enabled == 0)&&(pAdapter->WmmDesc.WmmUapsdEnabled==TRUE)) || ///UAPSD
             ((pAdapter->PPS_Enabled == 1)&&(pAdapter->sleepperiod != 0))){    ///PPS mode is enabled
            SetDnldMask(pAdapter->pDnLdParam, DNLDT_TX, L"LastPkt_Has_Set");
        }
    }
    return result;
}

static NDIS_STATUS	SetPktPwrBeforeTx(PVOID *pFuncArg, PSDIO_TX_PKT pDnldPacket)
{
	NDIS_STATUS 	result = NDIS_STATUS_SUCCESS;
#if 0
    ///ToDo: If PPS/UAPSD is enabled, => disable bus aggregation.
    /// reason: Performance is not important if PS mode is enabled
	PMRVDRV_ADAPTER pAdapter = (PMRVDRV_ADAPTER)pFuncArg;
    
    if ( pAdapter->WmmDesc.enabled )
    {
        pDnldPacket->Buf.TxDataBuf.Wcb.PowerMgmt = 0;
        if( pAdapter->PPS_Enabled == 0 ) //WMM_UAPSD   dralee_20060629
        {
            if( pAdapter->WmmDesc.WmmUapsdEnabled && CheckLastPacketIndication(pAdapter) )
            {
                pDnldPacket->Buf.TxDataBuf.Wcb.PowerMgmt |= MRVDRV_WCB_POWER_MGMT_LAST_PACKET;
            }
        }
    }
#endif ///0
	return result;
}

NDIS_STATUS
HandleTxSingleDoneEvent(
  IN PMRVDRV_ADAPTER Adapter
)
{
	PNDIS_PACKET pPacket;
    NDIS_STATUS result = NDIS_STATUS_SUCCESS;
    TR_TX_STATE TxState;

	DBGPRINT(DBG_TX | DBG_CRLF ,(L"+HandleTxSingleDoneEvent()\n"));
    GetTxStatus(Adapter->pTxParam, &TxState);
	if (TxState.state == TX_SENDING) {
		/*
		TxDone interrupt could be triggerred either by Tx packet or CMD has been sent (if it's SDIO)
		*/
		//RETAILMSG(1, (L"==> %s: SetTxDone\r\n", TEXT(__FUNCTION__)));
		pPacket = SetTxDone(Adapter->pTxParam);
	} else if (Adapter->HasFirmwareStarted == FALSE){
	    ///Firmware has not been downloaded..
	    /// => Tx-Done will be set if a segment of the firmware has been sent
	    goto FuncFinal;
	} else {
	    ///A CMD has been downloaded to firmware. Doing nothing~
	    ///=> Note: We found if th downloaded CMD is PS_Mode+Enter_PS, Tx-done may come back
	    ///     AFTER cmd_response.
	    ///     Platform: SDIO8686+ 9.70.3.p24
	}

	if ( wlan_ccx_isCurPacket(pPacket) )
	{
		//RETAILMSG(0, (L"==>%s: Tx-Done for CCX packet\r\n", TEXT(__FUNCTION__)));
		DBGPRINT(DBG_CCX, (L"[HandleTxSingleDone] It is CCX packet\n"));
		wlan_ccx_clrCurPacket();
	}

	/* Handle TxSingle done may be caused by txed data packet and command packet. 
	 If caused by command packet, Adapter->SentPacket == null. 
	 If caused by data packet, Adapter->SentPacket != null except the txed packet is null packet. 
	 When PPS is actived, null packet with last packet indication set may be sent to FW 
	*/
	///Processing if there is more TX pending
	//RETAILMSG(1, (L"%s, calling ReadyForNextTxPacket()\r\n", TEXT(__FUNCTION__)));
	//if (ReadyForNextTxPacket(Adapter->pTxParam)) {
	//	SetTxSoftIntr(Adapter);
	//} 
	{
             SendOneQueuedPacket(Adapter->pTxParam );
	}

FuncFinal:
	return result;
} 


///===================================================================

NDIS_STATUS InitMrvDrvAdapter(PMRVDRV_ADAPTER pAdapter)
{
	NDIS_STATUS		Result;
	TXINIT			TxInit;
	INTINIT			IntInit;
	RXINIT			RxInit;
	

	///Initialize Tx_Param
	TxInit.pAdapter = pAdapter;
	TxInit.MrvDrvAdapterHdl = pAdapter->MrvDrvAdapterHdl;
    TxInit.MaxTxAggrCnt = (USHORT)pAdapter->MaxTxAggrCnt;
	Result = InitTxParam(&pAdapter->pTxParam, &TxInit);
	if (Result != NDIS_STATUS_SUCCESS) {
		goto FuncFinal;
	}
	RegisterFilterFuncForTx(pAdapter->pTxParam, TF_BEFORE, CheckBeforeTx, pAdapter);
	////RegisterFilterFuncForTx(pAdapter->pTxParam, TF_BEFORE, EnhancePSBeforeTx, pAdapter);
	RegisterFilterFuncForTx(pAdapter->pTxParam, TF_BEFORE, SetPktPwrBeforeTx, pAdapter);
    RegisterFilterFuncForTx(pAdapter->pTxParam, TF_AFTER, SetTxMaskForPwr, pAdapter);

	///Initialize Int_Param;
	IntInit.pAdapter = pAdapter;
	IntInit.IstThreadPriority = pAdapter->IstThreadPriority;
	Result = InitIntParam(&pAdapter->pIntParam, &IntInit);
	if (Result != NDIS_STATUS_SUCCESS) {
		goto FuncFinal;
	}

	///Initialize Rx_Param
	RxInit.pAdapter = pAdapter;
	RxInit.MrvDrvAdapterHdl = pAdapter->MrvDrvAdapterHdl;
	Result = InitRxParam(&pAdapter->pRxParam, &RxInit);
	if (Result != NDIS_STATUS_SUCCESS) {
		goto FuncFinal;
	}

	///Initialize the download path
	InitDnldParam(&pAdapter->pDnLdParam);
	
FuncFinal:
	return Result;
}

NDIS_STATUS DeinitMrvDrvAdapter(PMRVDRV_ADAPTER pAdapter)
{
	NDIS_STATUS		Result = NDIS_STATUS_SUCCESS;

	DeinitTxParam(pAdapter->pTxParam);
	pAdapter->pTxParam = NULL;

	DeinitIntParam(pAdapter->pIntParam);
	pAdapter->pIntParam = NULL;

	DeinitRxParam(pAdapter->pRxParam);
	pAdapter->pRxParam = NULL;

	DeinitDnldParam(&pAdapter->pDnLdParam);
	pAdapter->pDnLdParam = NULL;

	return Result;
}



PWMM_DESC GetWMMDesc(PMRVDRV_ADAPTER pAdapter)
{
	return &pAdapter->WmmDesc;
}

///=====================================================================
void SetTxSoftIntr(PMRVDRV_ADAPTER pAdapter)
{
    //UINT    PktLen = GetTxPktLen(pAdapter->pTxParam);
    // Trigger the TxThread!
    SetSoftIntr(pAdapter->pIntParam, MRVL_SOFT_INT_TxRequest);
    SetEvent(GetIstWaitEvent(pAdapter->pIntParam));

	return;
}

void SetCmdSoftIntr(PMRVDRV_ADAPTER pAdapter)
{
    // Trigger the TxThread!
	SetSoftIntr(pAdapter->pIntParam, MRVL_SOFT_INT_CMD);
	SetEvent(GetIstWaitEvent(pAdapter->pIntParam));
	return;
}

///============================================================================
/**
 *  @fn: InitDnldParam()
 *  @breif: Initialize DnldParam module.
 *  @param: ipDnLdParam   Pointer to PDNLDPARAM
 *    
 *  Return Value: NDIS_STATUS_SUCCESS
 * 
 *  Notes:               
 *
 *****************************************************************************/
static NDIS_STATUS	InitDnldParam(IN PPVOID ipDnLdParam)
{
	NDIS_STATUS result = NDIS_STATUS_SUCCESS;
	PDNLDPARAM	pDnLdParam;

	pDnLdParam = *ipDnLdParam = &DnldParam;
	InitializeCriticalSection(&pDnLdParam->DnLdCS);
	pDnLdParam->TicketNumber = 0;
	pDnLdParam->FuncCnt = 0;
	NdisZeroMemory(pDnLdParam->DnLdRmLst, sizeof(pDnLdParam->DnLdRmLst));
    NdisZeroMemory(pDnLdParam->mask, sizeof(pDnLdParam->mask));
	return result;
}

/**
 *  @fn: DeinitDnldParam()
 *  @breif: Deinitialize DnldParam module.
 *  @param: ipDnLdParam   Pointer to PDNLDPARAM
 *    
 *  Return Value: NDIS_STATUS_SUCCESS
 * 
 *  Notes:               
 *
 *****************************************************************************/
static NDIS_STATUS	DeinitDnldParam(IN PVOID ipDnLdParam)
{
	NDIS_STATUS result = NDIS_STATUS_SUCCESS;
	PDNLDPARAM	pDnLdParam = (PDNLDPARAM)ipDnLdParam;

	DeleteCriticalSection(&pDnLdParam->DnLdCS);
    NdisZeroMemory(pDnLdParam->mask, sizeof(pDnLdParam->mask));

	return result;
}

static void DnldResumeFunc(PDNLDPARAM	pDnLdParam)
{
    int i = 0, j;
    DNLDRMNODE	DnLdRmNode;

    EnterCriticalSection(&pDnLdParam->DnLdCS);
    while (pDnLdParam->FuncCnt > 0) {
        if (pDnLdParam->mask[pDnLdParam->DnLdRmLst[i].type] == TRUE) {
            ///The function of this node has been masked out. Skip the trigger again.
            i++;
        }
        if (i >= pDnLdParam->FuncCnt) {
            break;
        }
        /// No need to increasing "i" => Wa always fetch the 1st available node, if it's not masked
        NdisMoveMemory(&DnLdRmNode, &pDnLdParam->DnLdRmLst[i], sizeof(DnLdRmNode));
        pDnLdParam->FuncCnt --;
        for (j=i ; j<pDnLdParam->FuncCnt ; j++) {
            //Move the node one step ahead
            NdisMoveMemory(&pDnLdParam->DnLdRmLst[j], &pDnLdParam->DnLdRmLst[j+1], sizeof(DnLdRmNode));
        }
        ///Clear the last node
        NdisZeroMemory(&pDnLdParam->DnLdRmLst[j], sizeof(DNLDRMNODE));
        ASSERT(DnLdRmNode.pFunc != NULL);
        LeaveCriticalSection(&pDnLdParam->DnLdCS);
		DnLdRmNode.pFunc(DnLdRmNode.pArg);
		EnterCriticalSection(&pDnLdParam->DnLdCS);
	}
    LeaveCriticalSection(&pDnLdParam->DnLdCS);
    return;
}

/**
 *  @fn: RequestDnldTicket()
 *  @breif: Request a download ticket. The CMD or TX... module should start to be processed
 *          if it can get a correct ticket (>0)
 *  @param: ipDnLdParam     Pointer to PDNLDPARAM
 *  @param: DnLdType        Type of the download path
 *  @param: pResumeFunc     If failed to get the ticket, which function to be retriggerred 
 *          while the resource is available
 *  @param: pFuncParam      Parameter of the function
 *    
 *  Return Value: int: Download ticket
 * 
 *  Notes:               
 *
 *****************************************************************************/
int RequestDnldTicket(IN PVOID ipDnLdParam, 
                            DNLD_TYPE DnLdType, 
                            DNLDRESUMEFUNC pResumeFunc, 
                            PVOID pFuncParam)
{
	PDNLDPARAM	pDnLdParam = (PDNLDPARAM)ipDnLdParam;
	int			TicketNum;
	PDNLDRMNODE	pDnLdRmNode;
    static USHORT   MagicNum;   ///This is just a number that we hope it to be different & not zero everytime this function is called
    USHORT          i;

	EnterCriticalSection(&pDnLdParam->DnLdCS);
	if ((pDnLdParam->TicketNumber == 0) && 
        (pDnLdParam->mask[DnLdType] == FALSE)) {
		///To return a number which is not 0 & is different everytime to be called
		TicketNum = pDnLdParam->TicketNumber = MagicNum|1;
        MagicNum +=2;
	} else {
	    for (i=0 ; i< pDnLdParam->FuncCnt ; i++) {
            pDnLdRmNode = &pDnLdParam->DnLdRmLst[i];
            if ((pDnLdRmNode->pFunc == pResumeFunc) && 
                (pDnLdRmNode->pArg == pFuncParam) &&
                (pDnLdRmNode->type == DnLdType)) {
                ///This resume function has already existed in the list
                TicketNum = 0;
                goto FuncFinal;
            }
	    }
		if ((pDnLdParam->FuncCnt < MAX_DNLDRESUMEFUNCS) && 
            (pResumeFunc != NULL)) {
			pDnLdRmNode = &pDnLdParam->DnLdRmLst[pDnLdParam->FuncCnt];

			pDnLdRmNode->pFunc = pResumeFunc;
			pDnLdRmNode->pArg = pFuncParam;
            pDnLdRmNode->type = DnLdType;
            pDnLdParam->FuncCnt ++;
		}
        if (pDnLdParam->FuncCnt == MAX_DNLDRESUMEFUNCS) {
            RETAILMSG(1, (L"%s: Warning, Reach the maximum pending resuming function count(%d)\r\n", 
                            TEXT(__FUNCTION__), 
                            pDnLdParam->FuncCnt));
        }
		TicketNum = 0;
	}
FuncFinal:
	LeaveCriticalSection(&pDnLdParam->DnLdCS);

	return TicketNum;
}

/**
 *  @fn: ReleaseDnldTicket()
 *  @breif: Release the download ticket the module has gotten & call the registered resuming functions
 *  @param: ipDnLdParam     Pointer to PDNLDPARAM
 *  @param: TicketNumber    The ticket which has been requested
 *    
 *  Return Value: None
 * 
 *  Notes:               
 *
 *****************************************************************************/
VOID ReleaseDnldTicket(IN PVOID ipDnLdParam, int TicketNumber)
{
	PDNLDPARAM	pDnLdParam = (PDNLDPARAM)ipDnLdParam;

	EnterCriticalSection(&pDnLdParam->DnLdCS);
	if(TicketNumber == pDnLdParam->TicketNumber) {
		pDnLdParam->TicketNumber = 0;
	} else {
		///No idea where is this ticket from ...
		LeaveCriticalSection(&pDnLdParam->DnLdCS);
		RETAILMSG(1, (L"%s: Error: No idea where is the ticket (%d, %d) from \r\n", TEXT(__FUNCTION__),TicketNumber, pDnLdParam->TicketNumber));
		goto FuncFinal;
	}
    LeaveCriticalSection(&pDnLdParam->DnLdCS);
    DnldResumeFunc(pDnLdParam);
FuncFinal:
	

	return;
}

/**
 *  @fn: SetDnldMask()
 *  @breif: The download path should be blocked a while sometimes. Set to mask to make it
 *  @param: ipDnLdParam     Pointer to PDNLDPARAM
 *  @param: DnLdType        Download type to be blocked
 *  @param: reason          The reason to block the path (debug purpose)
 *    
 *  Return Value: None
 * 
 *  Notes:               
 *
 *****************************************************************************/
VOID SetDnldMask(IN PVOID ipDnLdParam, DNLD_TYPE DnLdType, TRNREASON reason)
{
    PDNLDPARAM	pDnLdParam = (PDNLDPARAM)ipDnLdParam;

    pDnLdParam->mask[DnLdType] = TRUE;
    NdisMoveMemory(pDnLdParam->MaskReason[DnLdType], reason, sizeof(TRNREASON));
    return;
}

/**
 *  @fn: ClrDnldMask()
 *  @breif: Clear the download mask
 *  @param: ipDnLdParam     Pointer to PDNLDPARAM
 *  @param: DnLdType        Download type to be blocked
 *  @param: reason          The reason to block the path (debug purpose)
 *    
 *  Return Value: None
 * 
 *  Notes:               
 *
 *****************************************************************************/
VOID ClrDnldMask(IN PVOID ipDnLdParam, IN DNLD_TYPE DnLdType, TRNREASON reason)
{
    PDNLDPARAM	pDnLdParam = (PDNLDPARAM)ipDnLdParam;
    
    pDnLdParam->mask[DnLdType] = FALSE;
    ///Clear the mask
    NdisMoveMemory(pDnLdParam->MaskReason[DnLdType], reason, sizeof(TRNREASON));
    ///Some functions may be blocked by this mask => trigger it again.
    DnldResumeFunc(pDnLdParam);
    return;
}

/**
 *  @fn: ShowDnldStatus()
 *  @breif: Show the download status (debug purpose)
 *  @param: ipDnLdParam     Pointer to PDNLDPARAM
 *    
 *  Return Value: None
 * 
 *  Notes:               
 *
 *****************************************************************************/
VOID ShowDnldStatus(IN PVOID ipDnLdParam)
{
    PDNLDPARAM	pDnLdParam = (PDNLDPARAM)ipDnLdParam;

    RETAILMSG(1, (L"DnldPath: (TicketNumber, Mask[DNLDT_TX], Mask[DNLDT_CMD]): (%d, %xh, %xh, %s, %s)\r\n", 
                                pDnLdParam->TicketNumber,
                                pDnLdParam->mask[0], pDnLdParam->mask[1],
                                pDnLdParam->MaskReason[0], pDnLdParam->MaskReason[1]));
    return;
}

